/*--------------------------------------------------------
Copyright (c) 2010 Apolo Pena

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------*/
package org.flashalchemy.ezlib.ezlog.targets
{
    import mx.core.mx_internal;
    import mx.logging.ILogger;
    import mx.logging.LogEvent;
    import mx.logging.LogEventLevel;
    import mx.logging.targets.LineFormattedTarget;
    use namespace mx_internal;


    /**
     * <b>Description:</b> The primary purpose of the EzTraceTarget class is to provide more options for formatting
     * Log output, thus saving precious horizontal screen space in the console or whatever target
     * output is sent to.<br/>The secondary purpose of the EzTraceTarget class is to provide a standard
     * default configuration for Log output that all Engineers and Developers can use in their
     * projects.
     *
     * <p><b>Usage:</b> The are two main techniques when using EzTraceTarget class. The first technique
     *  is to use implicit instantiaion to create the object from the class, meaning the class is instantiated
     *  and passed to a method but is not assigned to a variable. If the first technique is used then there
     *  is no need to Flush the Log using a <code>Log.flush()</code> call prior to instantiating the class.
     *  Use this technique when initializing the Singleton class EzLog.<p/>
     *  <i>Example using technique 1 (implicit instantiaion): </i><p/>
     *  &nbsp;&nbsp;&nbsp;&nbsp;<code>EzLog.getInstance().initialize(new EzTraceTarget());</code>
     *  <p>The second technique uses direct instantiation, meaning the EzTraceTarget class is instantiated
     *  and assigned to a local or class variable. Unfortunately at the time of writing this documentation
     *  any class that extends <code>mx.logging.targets.TraceTarget</code> and uses direct instantiaton
     *  (aka technique 2) automatially adds and additional target to the log. Keeping this in mind You must
     *  ensure that you flush the Log immediately before you directly instantiate the EzTraceTarget class. Use this
     *  technique when you need to change public properties of an EzTraceTarget object before assigning it back
     *  to the Log, such as when you need to add a filter or change the format of the output of the Log at runtime.<p/>
     *  <i>Example using technique 2 (direct instantiation): </i><br/>
     *  <pre>
     *      var logTarget:EzTraceTarget = new EzTraceTarget();
     *      var filters:Array = ['foo.bar.model.data.*', this];
     *      logTarget.filters = EzLog.getFilter(filters);
     *      logTarget.fieldSeparator = '<<<<>>>>';
     *      logger = EzLog.swapLogTarget(this, logTarget);
     * </pre>
     *
     *  <b>Properties: </b>The EzTraceTarget class inherits all public and protected variables
     *  of its superclass, and adds the following public variables:<p/>
     *
     *  <pre>
     *    prependMessageWithDelimiter="false | true"
     * 		 omitPackagePathFromCategory="false | true"
     * 		 truncateLevel="false | true"
     * 		 omitHours="false | true"
     * 		 omitMinutes="false | true"
     * 		 omitSeconds="false | true"
     * 		 messageDelimiter="msg: "
     * 		 millisecondLabel="ms"
     *  </pre>
     */
    public class EzTraceTarget extends LineFormattedTarget
    {
        public var omitLogInformation:Boolean = false;

        /**
         * <p/>Boolean flag that determines if a string delimiter should be used in the Log output between the Log information and the Log message.<br/>
         * Set this variable to <code>true</code> to allow the delimiter to be displayed in the Log output.<br/>
         * Set this variable to <code>false</code> to omit the displaying of the delimiter in the Log output.
         *
         * @default true
         */
        public var prependMessageWithDelimiter:Boolean = true;

        /**
         * <p/>Boolean flag that determines if the full class package is displayed as the category name in the Log output.<br/>
         * Set this variable to <code>true</code> to display just the Classname in the Log output.<br/>
         * Set this variable to <code>false</code> to display the full package path including the Classname in the Log output.<br/>
         * This variable is only respected if the includeCategory property is set to <code>true</code>.
         *
         * @default true
         * @see the public Boolean variable includeCategory
         */
        public var omitPackagePathFromCategory:Boolean = true;

        /**
         * <p/>Boolean flag that determines if the log level string should be truncated to one character in the Log output.<br/>
         * Set this variable to <code>true</code> to display just a single letter for the log level.<br/>
         * Set this variable to <code>false</code> if you want the log level string to stay as a full word (i.e. [WARN] instead of [W]) in the Log.<br/>
         * This variable is only respected if the includeLevel property is set to <code>true</code>.
         *
         * @default true
         * @see the public Boolean variable includeLevel
         */
        public var truncateLevel:Boolean = false;

        /**
         * <p/>Boolean flag that determines the formatting of the display of hours in the Log output.<br/>
         * Set this variable to <code>true</code> to omit the display of the hours in the Log output.<br/>
         * Set this variable to <code>false</code> if you want the hours to be displayed in the Log output.<br/>
         * This feature is usually used to save horizontal space in the console.
         *
         * @default true
         * @see the public Boolean variable omitHours
         */
        public var omitHours:Boolean = true;

        /**
         * <p/>Boolean flag that determines the formatting of the display of minutes in the Log output.<br/>
         * Set this variable to <code>true</code> to omit the display of the minutes in the Log output.<br/>
         * Set this variable to <code>false</code> if you want the minutes to appear in the Log output.<br/>
         * This variable is only respected if the omitHours variable is set to <code>true</code> and the
         * includeTime variable is set to <code>true</code>.<br/>
         * This feature is usually used to save horizontal space in the console.
         *
         * @default true
         * @see the public Boolean variable omitHours
         * @see the public Boolean variable includeTime
         */
        public var omitMinutes:Boolean = true;

        /**
         * <p/>Boolean flag that determines the formatting of the display of seconds in the Log output.<br/>
         * Set this variable to <code>true</code> to omit the display of the seconds in the Log output.<br/>
         * Set this variable to <code>false</code> to show the full package path including the Classname in the Log output.<br/>
         * This property is only respected if the omitHours and omitMinutes properties are set to <code>true</code>.<br/>
         * This variable is usually set to <code>true</code> to save horizontal space in the console.<br/>
         *
         * @default true
         * @see the public Boolean variable omitHours
         * @see the public Boolean variable omitMinutes
         */
        public var omitSeconds:Boolean = true;

        /**
         * <p/>String to append to the end of the Log message.<br/>Use this property to
         * make a clearer seperation between the Log Information and the Log Meessage in the Log Event.
         *
         * @default "msg: "
         */
        public var messageDelimiter:String = 'msg: ';

        /**
         * <p/>String to append to the end of the Timestamp in the Log if the inherited
         * public property includeTime is set to <code>true</code>.
         *
         * @default "ms"
         */
        public var milisecondLabel:String = 'ms';

        /**
         * <p/>String to append to the end of the Timestamp if the inherited property
         * includeTime is set to true.
         *
         * @default " "
         */ //--------------------------------------------------------------------------
        //
        //  Constructor
        //
        //--------------------------------------------------------------------------
        /**
         *  Constructor calls super() and sets default values for inherited public variables.<br/>
         *  Default values for inherited public properties are as follows:<br/>
         *  <pre>
         * 				level = <code>mx.logging.LogEventLevel.DEBUG</code>
         * 				includeTime = <code>true</code>
         * 				includeCategory = <code>true</code>
         * 				includeLevel = <code>true</code>
         * </pre>
         */
        public function EzTraceTarget()
        {
            super();
            fieldSeparator = ' ';
            level = LogEventLevel.DEBUG;
            includeTime = true;
            includeCategory = true;
            includeLevel = true;
        }

        //--------------------------------------------------------------------------
        //
        //  Overridden Methods
        //
        //--------------------------------------------------------------------------
        /**
         *  @private
         *  This method outputs the specified message directly to
         *  <code>trace()</code>.
         *  All output will be directed to flashlog.txt by default.
         *
         *  @param message String containing preprocessed log message which may
         *  include time, date, category, etc. based on property settings,
         *  such as <code>includeDate</code>, <code>includeCategory</code>, etc.
         */
        override mx_internal function internalLog(message:String):void
        {
            trace(message);
        }

        //--------------------------------------------------------------------------
        //
        //  Overridden Event Handlers
        //
        //--------------------------------------------------------------------------
        /**
         *  This overidden event handler handles a <code>LogEvent</code> from an associated logger.
         *  A target uses this method to translate the event into the appropriate
         *  format for transmission, storage, or display.<br/>
         *  This method is called only if the event's level is in range of the
         *  target's level.
         *
         *  @param event The <code>LogEvent</code> handled by this method.
         */
        override public function logEvent(event:LogEvent):void
        {
            var date:String = '';
            if (includeDate || includeTime)
            {
                var d:Date = new Date();
                if (includeDate)
                {
                    date = Number(d.getMonth() + 1).toString() + '/' + d.getDate().toString() + '/' + d.getFullYear() + fieldSeparator;
                }
                if (includeTime)
                {
                    if (omitHours)
                    {
                        date = padTime(d.getMinutes()) + ':' + padTime(d.getSeconds()) + '.' + padTime(d.getMilliseconds(), true) + milisecondLabel + fieldSeparator;
                        if (omitMinutes)
                        {
                            date = padTime(d.getSeconds()) + '.' + padTime(d.getMilliseconds(), true) + milisecondLabel + fieldSeparator;
                            if (omitSeconds)
                            {
                                date = '';
                            }
                        }
                    }
                    else
                    {
                        date += padTime(d.getHours()) + ':' + padTime(d.getMinutes()) + ':' + padTime(d.getSeconds()) + '.' + padTime(d.getMilliseconds(), true) + milisecondLabel + fieldSeparator;
                    }
                }
            }
            var level:String = '';
            if (includeLevel)
            {
                var levelLabel:String = LogEvent.getLevelString(event.level);
                if (truncateLevel)
                {
                    levelLabel = levelLabel.slice(0, 1);
                }
                level = '[' + levelLabel + ']' + fieldSeparator;
            }
            var category:String;
            if (includeCategory)
            {
                if (omitPackagePathFromCategory)
                {
                    category = getClassNameFromCategoryString(ILogger(event.target).category) + fieldSeparator;
                }
                else
                {
                    category = ILogger(event.target).category + fieldSeparator;
                }
            }
            else
            {
                category = '';
            }
            var delimiter:String;
            prependMessageWithDelimiter ? delimiter = messageDelimiter : delimiter = '';
            if (omitLogInformation)
            {
                internalLog(event.message);
            }
            else
            {
                internalLog(date + level + category + delimiter + event.message);
            }
        }

        //--------------------------------------------------------------------------
        //
        //  Methods
        //
        //--------------------------------------------------------------------------
        /**
         * This method extracts the classname string from a category string
         *
         * @ return
         */
        protected function getClassNameFromCategoryString(categoryString:String):String
        {
            var _return:String = categoryString.slice(categoryString.lastIndexOf(".") + 1, categoryString.length);
            return _return;
        }

        /**
         * This method formats a number into a human readable timestamp
         *
         * @return
         */
        protected function padTime(num:Number, millis:Boolean = false):String
        {
            if (millis)
            {
                if (num < 10)
                    return "00" + num.toString();
                else if (num < 100)
                    return "0" + num.toString();
                else
                    return num.toString();
            }
            else
            {
                return num > 9 ? num.toString() : "0" + num.toString();
            }
        }
    }
}

